import { Block } from "./Block.js";
import { BlockController } from "./BlockController.js";
import { CommandPanel } from "./CommandPanel.js";
import { IEvent } from "./Eventable.js";
import { InfoPanel } from "./InfoPanel.js";
import { Puzzle } from "./Puzzle.js";
import { ByWhat, StateManager } from "./StateManager.js";
import { Timer } from "./Timer.js";

export interface ITetrisOptions {
    width: number;
    height: number;
    puzzle: JQuery | string;
    preview: JQuery | string;
    restart: JQuery | string;
    pause: JQuery | string;
    score: JQuery | string;
    speed: JQuery | string;
    initialSpeed: number;
}

const DEFAULT_OPTIONS: ITetrisOptions = {
    width: 10,
    height: 18,
    puzzle: "#puzzle",
    preview: "#preview",
    restart: "#restart",
    pause: "#pause",
    score: "#score",
    speed: "#speed",
    initialSpeed: 5
};

export class Tetris {
    #options: ITetrisOptions;
    #blockController: BlockController;
    #timer: Timer;
    #states: StateManager;
    #puzzle: Puzzle;
    #preview: Puzzle;
    #infoPanel: InfoPanel;
    #commandPanel: CommandPanel;

    constructor(_options: Partial<ITetrisOptions> = {}) {
        const options: Readonly<ITetrisOptions> = this.#options = { ...DEFAULT_OPTIONS, ..._options };

        this.#puzzle = Puzzle.create(options.width, options.height, options.puzzle);
        this.#preview = Puzzle.create(4, 4, options.preview);

        this.#blockController = new BlockController(options.width, options.height);
        this.#commandPanel = new CommandPanel(options.restart, options.pause);

        this.#infoPanel = new InfoPanel(options.score, options.speed);
        this.#infoPanel.speed = options.initialSpeed;

        this.#states = new StateManager();
        this.#timer = new Timer(this.#infoPanel.interval);

        this.setup();
    }

    get options(): Readonly<ITetrisOptions> {
        return Object.freeze(this.#options);
    }

    private setup(): void {
        this.setupEvents();
        this.setupKeyEvents();
    }

    private setupEvents(): void {
        this.#blockController
            .on("reset", () => {
                this.#infoPanel.score = 0;
                this.#timer.interval = this.#infoPanel.interval;
            })
            .on("nextChanged", (event: IEvent) => {
                this.#preview.block = event["block"] as Block;
                this.#preview.render();
            })
            .on("currentChanged", (event: IEvent) => {
                this.#puzzle.block = event["block"] as Block;
                this.#preview.render();
            })
            .on("fastening", this.#states.pause.bind(this.#states, ByWhat.CODE))
            .on("fastened", () => {
                this.#states.resume(ByWhat.CODE);
            })
            .on("erasedRows", (event: IEvent) => {
                this.#infoPanel.addByLevel(event["rowsCount"] as number);
                this.#puzzle.fastened = this.#blockController.fastenMatrix();
                this.#puzzle.block = null;
            })
            .on("gameover", this.#states.over.bind(this.#states))
            .on("render", (event: IEvent) => {
                if (event["shouldFasten"]) {
                    this.#puzzle.fastened = this.#blockController.fastenMatrix();
                }
                this.#puzzle.render();
            });

        this.#timer
            .on("performed", this.#blockController.process.bind(this.#blockController));

        this.#states
            .on("pause", this.#timer.stop.bind(this.#timer))
            .on("resume", this.#timer.restart.bind(this.#timer))
            .on("restart", this.#blockController.restart.bind(this.#blockController));

        this.#infoPanel
            .on("speedUp", (event: IEvent) => {
                this.#timer.interval = event["interval"] as number;
            });

        this.#commandPanel
            .on("pause", () => {
                if (this.#states.isPausedByManual) {
                    this.#states.resume(ByWhat.MANUAL);
                } else {
                    this.#states.pause(ByWhat.MANUAL);
                }
            })
            .on("restart", this.#states.restart.bind(this.#states));
    }

    private setupKeyEvents(): void {
        const controller = this.#blockController;

        const handlers = {
            // up
            "38": controller.rotate.bind(controller),

            // left
            "37": controller.moveLeft.bind(controller),

            // right
            "39": controller.moveRight.bind(controller),

            // down
            "40": controller.moveDown.bind(controller),

            // fall down to bottom
            "32": controller.fallDownDirectly.bind(controller),
        };

        $(document).on("keydown", e => {
            if (this.#states.isPaused) {
                return;
            }
            const handler = handlers[e.keyCode];
            if (handler) {
                handler();
            }
        });
    }

    run(): void {
        this.#blockController.run();
        this.#timer.restart();
    }
}
